package com.cl.photozoom;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Matrix.ScaleToFit;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;

import androidx.core.view.MotionEventCompat;

import com.cl.photozoom.gestures.OnGestureListener;
import com.cl.photozoom.gestures.VersionedGestureDetector;
import com.cl.photozoom.scrollerproxy.ScrollerProxy;

import java.lang.ref.WeakReference;

import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;

public class PhotoViewAttacher implements IPhotoView, View.OnTouchListener, OnGestureListener, ViewTreeObserver.OnGlobalLayoutListener {

    private Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
    
    int ZOOM_DURATION = DEFAULT_ZOOM_DURATION;

    static final int EDGE_NONE = -1;
    
    static final int EDGE_LEFT = 0;
    
    static final int EDGE_RIGHT = 1;
    
    static final int EDGE_BOTH = 2;

    static int SINGLE_TOUCH = 1;

    private float mMinScale = DEFAULT_MIN_SCALE;
    
    private float mMidScale = DEFAULT_MID_SCALE;
    
    private float mMaxScale = DEFAULT_MAX_SCALE;

    private boolean mAllowParentInterceptOnEdge = true;
    
    private boolean mBlockParentIntercept = false;

    private static void checkZoomLevels(float minZoom, float midZoom, float maxZoom) {
    	
        if (minZoom >= midZoom) {
        	
            throw new IllegalArgumentException("Minimum zoom has to be less than Medium zoom. Call setMinimumZoom() with a more appropriate value");
            
        } else if (midZoom >= maxZoom) {
        	
            throw new IllegalArgumentException("Medium zoom has to be less than Maximum zoom. Call setMaximumZoom() with a more appropriate value");
            
        }
        
    }
    
    private static boolean hasDrawable(ImageView imageView) {
    	
        return null != imageView && null != imageView.getDrawable();
        
    }
 
    private static boolean isSupportedScaleType(final ScaleType scaleType) {
    	
        if (null == scaleType) {
        	
            return false;
            
        }

        switch (scaleType) {
        
            case MATRIX:
            	
                throw new IllegalArgumentException(scaleType.name() + " is not supported in PhotoView");

            default:
            	
                return true;
                
        }
        
    }
 
    private static void setImageViewScaleTypeMatrix(ImageView imageView) { 
    	
        if (null != imageView && !(imageView instanceof IPhotoView)) {
        	
            if (!ScaleType.MATRIX.equals(imageView.getScaleType())) {
            	
                imageView.setScaleType(ScaleType.MATRIX);
                
            }
            
        }
        
    }

    private WeakReference<ImageView> mImageView;
 
    private GestureDetector mGestureDetector;
    
    private com.cl.photozoom.gestures.GestureDetector mScaleDragDetector;
 
    private final Matrix mBaseMatrix = new Matrix();
    
    private final Matrix mDrawMatrix = new Matrix();
    
    private final Matrix mSuppMatrix = new Matrix();
    
    private final RectF mDisplayRect = new RectF();
    
    private final float[] mMatrixValues = new float[9];
 
    private OnMatrixChangedListener mMatrixChangeListener;
    
    private OnPhotoTapListener mPhotoTapListener;
    
    private OnViewTapListener mViewTapListener;
    
    private OnLongClickListener mLongClickListener;
    
    private OnScaleChangeListener mScaleChangeListener;
    
    private OnSingleFlingListener mSingleFlingListener;
     
    private int mIvTop, mIvRight, mIvBottom, mIvLeft;
    
    private FlingRunnable mCurrentFlingRunnable;
    
    private int mScrollEdge = EDGE_BOTH;
    
    private float mBaseRotation;

    private boolean mZoomEnabled;
    
    private ScaleType mScaleType = ScaleType.FIT_CENTER;

    public PhotoViewAttacher(ImageView imageView) {
    	
        this(imageView, true);
        
    }

    public PhotoViewAttacher(ImageView imageView, boolean zoomable) {
    	
        mImageView = new WeakReference<ImageView>(imageView);

        imageView.setDrawingCacheEnabled(true);
        
        imageView.setOnTouchListener(this);

        ViewTreeObserver observer = imageView.getViewTreeObserver();
        
        if (null != observer){
        	
        	observer.addOnGlobalLayoutListener(this);
        	
        }
            
        setImageViewScaleTypeMatrix(imageView);

        if (imageView.isInEditMode()) {
        	
            return;
            
        } 
        
        mScaleDragDetector = VersionedGestureDetector.newInstance(imageView.getContext(), this);

        mGestureDetector = new GestureDetector(imageView.getContext(), new GestureDetector.SimpleOnGestureListener() {
 
                    @Override
                    public void onLongPress(MotionEvent e) {
                    	
                        if (null != mLongClickListener) {
                        	
                            mLongClickListener.onLongClick(getImageView());
                            
                        }
                        
                    }

                    @Override
                    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
                    	
                        if (mSingleFlingListener != null) {
                        	
                            if (getScale() > DEFAULT_MIN_SCALE) {
                            	
                                return false;
                                
                            }

                            if (MotionEventCompat.getPointerCount(e1) > SINGLE_TOUCH || MotionEventCompat.getPointerCount(e2) > SINGLE_TOUCH) {
                                
                            	return false;
                            
                            }

                            return mSingleFlingListener.onFling(e1, e2, velocityX, velocityY);
                            
                        }
                        
                        return false;
                        
                    }
                    
                });

        mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));
        
        mBaseRotation = 0.0f;
 
        setZoomable(zoomable);
        
    }

    @Override
    public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) {
    	
        if (newOnDoubleTapListener != null) {
        	
            this.mGestureDetector.setOnDoubleTapListener(newOnDoubleTapListener);
            
        } else {
        	
            this.mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));
            
        }
        
    }

    @Override
    public void setOnScaleChangeListener(OnScaleChangeListener onScaleChangeListener) {
    	
        this.mScaleChangeListener = onScaleChangeListener;
        
    }

    @Override
    public void setOnSingleFlingListener(OnSingleFlingListener onSingleFlingListener) {
    	
        this.mSingleFlingListener = onSingleFlingListener;
        
    }

    @Override
    public boolean canZoom() {
    	
        return mZoomEnabled;
        
    }
 
    @SuppressWarnings("deprecation")
    public void cleanup() {
    	
        if (null == mImageView) {
        	
            return;
            
        }

        final ImageView imageView = mImageView.get();

        if (null != imageView) {
        	
            ViewTreeObserver observer = imageView.getViewTreeObserver();
            
            if (null != observer && observer.isAlive()) {
            	
                observer.removeGlobalOnLayoutListener(this);
                
            }
 
            imageView.setOnTouchListener(null);
 
            cancelFling();
            
        }

        if (null != mGestureDetector) {
        	
            mGestureDetector.setOnDoubleTapListener(null);
            
        }
 
        mMatrixChangeListener = null;
        
        mPhotoTapListener = null;
        
        mViewTapListener = null;
 
        mImageView = null;
        
    }

    @Override
    public RectF getDisplayRect() {
    	
        checkMatrixBounds();
        
        return getDisplayRect(getDrawMatrix());
        
    }

    @Override
    public boolean setDisplayMatrix(Matrix finalMatrix) {
    	
        if (finalMatrix == null) {
        	
            throw new IllegalArgumentException("Matrix cannot be null");
            
        }

        ImageView imageView = getImageView();
        
        if (null == imageView) {
        	
            return false;
            
        }

        if (null == imageView.getDrawable()) {
        	
            return false;
            
        }

        mSuppMatrix.set(finalMatrix);
        
        setImageViewMatrix(getDrawMatrix());
        
        checkMatrixBounds();

        return true;
        
    }

    public void setBaseRotation(final float degrees) {
    	
        mBaseRotation = degrees % 360;
        
        update();
        
        setRotationBy(mBaseRotation);
        
        checkAndDisplayMatrix();
        
    }

    @Override
    public void setRotationTo(float degrees) {
    	
        mSuppMatrix.setRotate(degrees % 360);
        
        checkAndDisplayMatrix();
        
    }

    @Override
    public void setRotationBy(float degrees) {
    	
        mSuppMatrix.postRotate(degrees % 360);
        
        checkAndDisplayMatrix();
        
    }

    public ImageView getImageView() {
    	
        ImageView imageView = null;

        if (null != mImageView) {
        	
            imageView = mImageView.get();
            
        }
 
        if (null == imageView) {
        	
            cleanup(); 
            
        }

        return imageView;
        
    }

    @Override
    public float getMinimumScale() {
    	
        return mMinScale;
        
    }

    @Override
    public float getMediumScale() {
    	
        return mMidScale;
        
    }

    @Override
    public float getMaximumScale() {
    	
        return mMaxScale;
        
    }

    @Override
    public float getScale() {
    	
        return (float) Math.sqrt((float) Math.pow(getValue(mSuppMatrix, Matrix.MSCALE_X), 2) + (float) Math.pow(getValue(mSuppMatrix, Matrix.MSKEW_Y), 2));
    
    }

    @Override
    public ScaleType getScaleType() {
   
    	return mScaleType;
    
    }

    @Override
    public void onDrag(float dx, float dy) {
    	
        if (mScaleDragDetector.isScaling()) {
        	
            return;
            
        } 

        ImageView imageView = getImageView();
        
        mSuppMatrix.postTranslate(dx, dy);
        
        checkAndDisplayMatrix();
 
        ViewParent parent = imageView.getParent();
        
        if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling() && !mBlockParentIntercept) {
        	
            if (mScrollEdge == EDGE_BOTH || (mScrollEdge == EDGE_LEFT && dx >= 1f) || (mScrollEdge == EDGE_RIGHT && dx <= -1f)) {
            	
                if (null != parent) {
                	
                    parent.requestDisallowInterceptTouchEvent(false);
                    
                }
                
            }
            
        } else {
        	
            if (null != parent) {
            	
                parent.requestDisallowInterceptTouchEvent(true);
                
            }
            
        }
        
    }

    @Override
    public void onFling(float startX, float startY, float velocityX, float velocityY) { 
    	
        ImageView imageView = getImageView();
        
        mCurrentFlingRunnable = new FlingRunnable(imageView.getContext());
        
        mCurrentFlingRunnable.fling(getImageViewWidth(imageView), getImageViewHeight(imageView), (int) velocityX, (int) velocityY);
        
        imageView.post(mCurrentFlingRunnable);
        
    }

    @Override
    public void onGlobalLayout() {
    	
        ImageView imageView = getImageView();

        if (null != imageView) {
        	
            if (mZoomEnabled) {
            	
                final int top = imageView.getTop();
                
                final int right = imageView.getRight();
                
                final int bottom = imageView.getBottom();
                
                final int left = imageView.getLeft();
 
                if (top != mIvTop || bottom != mIvBottom || left != mIvLeft || right != mIvRight) { 
                	
                    updateBaseMatrix(imageView.getDrawable());

                    mIvTop = top;
                    
                    mIvRight = right;
                    
                    mIvBottom = bottom;
                    
                    mIvLeft = left;
                    
                }
                
            } else {
            	
                updateBaseMatrix(imageView.getDrawable());
                
            }
            
        }
        
    }

    @Override
    public void onScale(float scaleFactor, float focusX, float focusY) { 

        if ((getScale() < mMaxScale || scaleFactor < 1f) && (getScale() > mMinScale || scaleFactor > 1f)) {
        	
            if (null != mScaleChangeListener) {
            	
                mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY);
                
            }
            
            mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
            
            checkAndDisplayMatrix();
            
        }
        
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouch(View v, MotionEvent ev) {
    	
        boolean handled = false;

        if (mZoomEnabled && hasDrawable((ImageView) v)) {
        	
            ViewParent parent = v.getParent();
            
            switch (ev.getAction()) {
            
                case ACTION_DOWN: 
                	
                    if (null != parent) {
                    	
                        parent.requestDisallowInterceptTouchEvent(true);
                        
                    }
 
                    cancelFling();
                    
                    break;

                case ACTION_CANCEL:
                case ACTION_UP:
                	
                    if (getScale() < mMinScale) {
                    	
                        RectF rect = getDisplayRect();
                        
                        if (null != rect) {
                        	
                            v.post(new AnimatedZoomRunnable(getScale(), mMinScale, rect.centerX(), rect.centerY()));
                            
                            handled = true;
                            
                        }
                        
                    }
                    
                    break;
                    
            }
 
            if (null != mScaleDragDetector) {
            	
                boolean wasScaling = mScaleDragDetector.isScaling();
                
                boolean wasDragging = mScaleDragDetector.isDragging();

                handled = mScaleDragDetector.onTouchEvent(ev);

                boolean didntScale = !wasScaling && !mScaleDragDetector.isScaling();
                
                boolean didntDrag = !wasDragging && !mScaleDragDetector.isDragging();

                mBlockParentIntercept = didntScale && didntDrag;
                
            }
 
            if (null != mGestureDetector && mGestureDetector.onTouchEvent(ev)) {
            	
                handled = true;
                
            }

        }

        return handled;
        
    }

    @Override
    public void setAllowParentInterceptOnEdge(boolean allow) {
    	
        mAllowParentInterceptOnEdge = allow;
    
    }

    @Override
    public void setMinimumScale(float minimumScale) {
    	
        checkZoomLevels(minimumScale, mMidScale, mMaxScale);
        
        mMinScale = minimumScale;
        
    }

    @Override
    public void setMediumScale(float mediumScale) {
    	
        checkZoomLevels(mMinScale, mediumScale, mMaxScale);
        
        mMidScale = mediumScale;
        
    }

    @Override
    public void setMaximumScale(float maximumScale) {
    	
        checkZoomLevels(mMinScale, mMidScale, maximumScale);
        
        mMaxScale = maximumScale;
        
    }

    @Override
    public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) {
    	
        checkZoomLevels(minimumScale, mediumScale, maximumScale);
        
        mMinScale = minimumScale;
        
        mMidScale = mediumScale;
        
        mMaxScale = maximumScale;
        
    }

    @Override
    public void setOnLongClickListener(OnLongClickListener listener) {
    	
        mLongClickListener = listener;
        
    }

    @Override
    public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
    	
        mMatrixChangeListener = listener;
        
    }

    @Override
    public void setOnPhotoTapListener(OnPhotoTapListener listener) {
    	
        mPhotoTapListener = listener;
        
    } 
    
    OnPhotoTapListener getOnPhotoTapListener() {
    	
        return mPhotoTapListener;
        
    }

    @Override
    public void setOnViewTapListener(OnViewTapListener listener) {
    	
        mViewTapListener = listener;
        
    }
 
    OnViewTapListener getOnViewTapListener() {
    	
        return mViewTapListener;
        
    }

    @Override
    public void setScale(float scale) {
    	
        setScale(scale, false);
        
    }

    @Override
    public void setScale(float scale, boolean animate) {
    	
        ImageView imageView = getImageView();

        if (null != imageView) {
        	
            setScale(scale, (imageView.getRight()) / 2,  (imageView.getBottom()) / 2, animate);
            
        }
        
    }

    @Override
    public void setScale(float scale, float focalX, float focalY, boolean animate) {
    	
        ImageView imageView = getImageView();

        if (null != imageView) { 
        	
            if (scale < mMinScale || scale > mMaxScale) { 
            	
                return;
                
            }

            if (animate) {
            	
                imageView.post(new AnimatedZoomRunnable(getScale(), scale, focalX, focalY));
                
            } else {
            	
                mSuppMatrix.setScale(scale, scale, focalX, focalY);
                
                checkAndDisplayMatrix();
                
            }
            
        }
        
    }

    public void setZoomInterpolator(Interpolator interpolator) {
    	
        mInterpolator = interpolator;
        
    }

    @Override
    public void setScaleType(ScaleType scaleType) {
    	
        if (isSupportedScaleType(scaleType) && scaleType != mScaleType) {
        	
            mScaleType = scaleType; 
            
            update();
            
        }
        
    }

    @Override
    public void setZoomable(boolean zoomable) {
    	
        mZoomEnabled = zoomable;
        
        update();
        
    }

    public void update() {
    	
        ImageView imageView = getImageView();

        if (null != imageView) {
        	
            if (mZoomEnabled) { 
            	
                setImageViewScaleTypeMatrix(imageView);
 
                updateBaseMatrix(imageView.getDrawable());
                
            } else { 
            	
                resetMatrix();
                
            }
            
        }
        
    }
 
    @Override
    public void getDisplayMatrix(Matrix matrix) {
    	
        matrix.set(getDrawMatrix());
        
    }
 
    public void getSuppMatrix(Matrix matrix) {
    	
        matrix.set(mSuppMatrix);
        
    }

    private Matrix getDrawMatrix() {
    	
        mDrawMatrix.set(mBaseMatrix);
        
        mDrawMatrix.postConcat(mSuppMatrix);
        
        return mDrawMatrix;
        
    }

    private void cancelFling() {
    	
        if (null != mCurrentFlingRunnable) {
        	
            mCurrentFlingRunnable.cancelFling();
            
            mCurrentFlingRunnable = null;
            
        }
        
    }

    public Matrix getImageMatrix() {
    	
        return mDrawMatrix;
        
    }
 
    private void checkAndDisplayMatrix() {
    	
        if (checkMatrixBounds()) {
        	
            setImageViewMatrix(getDrawMatrix());
            
        }
        
    }

    private void checkImageViewScaleType() {
    	
        ImageView imageView = getImageView();
 
        if (null != imageView && !(imageView instanceof IPhotoView)) {
        	
            if (!ScaleType.MATRIX.equals(imageView.getScaleType())) {
            	
                throw new IllegalStateException("The ImageView's ScaleType has been changed since attaching a PhotoViewAttacher. You should call setScaleType on the PhotoViewAttacher instead of on the ImageView"  );
            
            }
        
        }
    
    }

    private boolean checkMatrixBounds() {
    	
        final ImageView imageView = getImageView();
        
        if (null == imageView) {
        	
            return false;
            
        }

        final RectF rect = getDisplayRect(getDrawMatrix());
        
        if (null == rect) {
        	
            return false;
            
        }

        final float height = rect.height(), width = rect.width();
        
        float deltaX = 0, deltaY = 0;

        final int viewHeight = getImageViewHeight(imageView);
        
        if (height <= viewHeight) {
        	
            switch (mScaleType) {
            
                case FIT_START:
                	
                    deltaY = -rect.top;
                    
                    break;
                    
                case FIT_END:
                	
                    deltaY = viewHeight - height - rect.top;
                    
                    break;
                    
                default:
                	
                    deltaY = (viewHeight - height) / 2 - rect.top;
                    
                    break;
                    
            }
            
        } else if (rect.top > 0) {
        	
            deltaY = -rect.top;
            
        } else if (rect.bottom < viewHeight) {
        	
            deltaY = viewHeight - rect.bottom;
            
        }

        final int viewWidth = getImageViewWidth(imageView);
        
        if (width <= viewWidth) {
        	
            switch (mScaleType) {
            
                case FIT_START:
                	
                    deltaX = -rect.left;
                    
                    break;
                    
                case FIT_END:
                	
                    deltaX = viewWidth - width - rect.left;
                    
                    break;
                    
                default:
                	
                    deltaX = (viewWidth - width) / 2 - rect.left;
                    
                    break;
                    
            }
            
            mScrollEdge = EDGE_BOTH;
            
        } else if (rect.left > 0) {
        	
            mScrollEdge = EDGE_LEFT;
            
            deltaX = -rect.left;
            
        } else if (rect.right < viewWidth) {
        	
            deltaX = viewWidth - rect.right;
            
            mScrollEdge = EDGE_RIGHT;
            
        } else {
        	
            mScrollEdge = EDGE_NONE;
            
        } 
        
        mSuppMatrix.postTranslate(deltaX, deltaY);
        
        return true;
        
    }

    private RectF getDisplayRect(Matrix matrix) {
    	
        ImageView imageView = getImageView();

        if (null != imageView) {
        	
            Drawable d = imageView.getDrawable();
            
            if (null != d) {
            	
                mDisplayRect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
                
                matrix.mapRect(mDisplayRect);
                
                return mDisplayRect;
                
            }
            
        }
        
        return null;
        
    }

    public Bitmap getVisibleRectangleBitmap() {
    	
        ImageView imageView = getImageView();
        
        return imageView == null ? null : imageView.getDrawingCache();
        
    }

    @Override
    public void setZoomTransitionDuration(int milliseconds) {
    	
        if (milliseconds < 0){
        	
        	 milliseconds = DEFAULT_ZOOM_DURATION;
        	
        }
           
        this.ZOOM_DURATION = milliseconds;
        
    }

    @Override
    public IPhotoView getIPhotoViewImplementation() {
    	
        return this;
        
    }
 
    private float getValue(Matrix matrix, int whichValue) {
    	
        matrix.getValues(mMatrixValues);
        
        return mMatrixValues[whichValue];
        
    }
 
    private void resetMatrix() {
    	
        mSuppMatrix.reset();
        
        setRotationBy(mBaseRotation);
        
        setImageViewMatrix(getDrawMatrix());
        
        checkMatrixBounds();
        
    }

    private void setImageViewMatrix(Matrix matrix) {
    	
        ImageView imageView = getImageView();
        
        if (null != imageView) {

            checkImageViewScaleType();
            
            imageView.setImageMatrix(matrix);
 
            if (null != mMatrixChangeListener) {
            	
                RectF displayRect = getDisplayRect(matrix);
                
                if (null != displayRect) {
                	
                    mMatrixChangeListener.onMatrixChanged(displayRect);
                    
                }
                
            }
            
        }
        
    }
 
    private void updateBaseMatrix(Drawable d) {
    	
        ImageView imageView = getImageView();
        
        if (null == imageView || null == d) {
        	
            return;
            
        }

        final float viewWidth = getImageViewWidth(imageView);
        
        final float viewHeight = getImageViewHeight(imageView);
        
        final int drawableWidth = d.getIntrinsicWidth();
        
        final int drawableHeight = d.getIntrinsicHeight();

        mBaseMatrix.reset();

        final float widthScale = viewWidth / drawableWidth;
        
        final float heightScale = viewHeight / drawableHeight;

        if (mScaleType == ScaleType.CENTER) {
        	
            mBaseMatrix.postTranslate((viewWidth - drawableWidth) / 2F, (viewHeight - drawableHeight) / 2F);

        } else if (mScaleType == ScaleType.CENTER_CROP) {
        	
            float scale = Math.max(widthScale, heightScale);
            
            mBaseMatrix.postScale(scale, scale);
            
            mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F, (viewHeight - drawableHeight * scale) / 2F);

        } else if (mScaleType == ScaleType.CENTER_INSIDE) {
        	
            float scale = Math.min(1.0f, Math.min(widthScale, heightScale));
            
            mBaseMatrix.postScale(scale, scale);
            
            mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F, (viewHeight - drawableHeight * scale) / 2F);

        } else {
        	
            RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight);
            
            RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight);

            if ((int) mBaseRotation % 180 != 0) {
            	
                mTempSrc = new RectF(0, 0, drawableHeight, drawableWidth);
                
            }

            switch (mScaleType) {
            
                case FIT_CENTER:
                	
                    mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER);
                    
                    break;

                case FIT_START:
                	
                    mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.START);
                    
                    break;

                case FIT_END:
                	
                    mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END);
                    
                    break;

                case FIT_XY:
                	
                    mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.FILL);
                    
                    break;
                    
                default:
                	break; 
                    
            }
            
        }

        resetMatrix();
        
    }

    private int getImageViewWidth(ImageView imageView) {
    	
        if (null == imageView){
        	
        	return 0;
        	
        }
            
        return imageView.getWidth() - imageView.getPaddingLeft() - imageView.getPaddingRight();
        
    }

    private int getImageViewHeight(ImageView imageView) {
    	
        if (null == imageView){
        	
        	return 0;
        	
        }
           
        return imageView.getHeight() - imageView.getPaddingTop() - imageView.getPaddingBottom();
        
    }
 
    public interface OnMatrixChangedListener {
         
        void onMatrixChanged(RectF rect);
        
    }

    public interface OnScaleChangeListener {
        
        void onScaleChange(float scaleFactor, float focusX, float focusY);
        
    }
 
    public interface OnPhotoTapListener {
 
        void onPhotoTap(View view, float x, float y);
 
        void onOutsidePhotoTap();
        
    }
 
    public interface OnViewTapListener {
 
        void onViewTap(View view, float x, float y);
    }
 
    public interface OnSingleFlingListener {
 
        boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
        
    }

    private class AnimatedZoomRunnable implements Runnable {

        private final float mFocalX, mFocalY;
        
        private final long mStartTime;
        
        private final float mZoomStart, mZoomEnd;

        public AnimatedZoomRunnable(final float currentZoom, final float targetZoom, final float focalX, final float focalY) {
        	
            mFocalX = focalX;
            
            mFocalY = focalY;
            
            mStartTime = System.currentTimeMillis();
            
            mZoomStart = currentZoom;
            
            mZoomEnd = targetZoom;
            
        }

        @Override
        public void run() {
        	
            ImageView imageView = getImageView();
            
            if (imageView == null) {
            	
                return;
                
            }

            float t = interpolate();
            
            float scale = mZoomStart + t * (mZoomEnd - mZoomStart);
            
            float deltaScale = scale / getScale();

            onScale(deltaScale, mFocalX, mFocalY);
 
            if (t < 1f) {
            	
                Compat.postOnAnimation(imageView, this);
                
            }
            
        }

        private float interpolate() {
        	
            float t = 1f * (System.currentTimeMillis() - mStartTime) / ZOOM_DURATION;
            
            t = Math.min(1f, t);
            
            t = mInterpolator.getInterpolation(t);
            
            return t;
            
        }
        
    }

    private class FlingRunnable implements Runnable {

        private final ScrollerProxy mScroller;
        
        private int mCurrentX, mCurrentY;

        public FlingRunnable(Context context) {
        	
            mScroller = ScrollerProxy.getScroller(context);
            
        }

        public void cancelFling() { 
        	
            mScroller.forceFinished(true);
            
        }

        public void fling(int viewWidth, int viewHeight, int velocityX, int velocityY) {
        	
            final RectF rect = getDisplayRect();
            
            if (null == rect) {
            	
                return;
                
            }

            final int startX = Math.round(-rect.left);
            
            final int minX, maxX, minY, maxY;

            if (viewWidth < rect.width()) {
            	
                minX = 0;
                
                maxX = Math.round(rect.width() - viewWidth);
                
            } else {
            	
                minX = maxX = startX;
                
            }

            final int startY = Math.round(-rect.top);
            
            if (viewHeight < rect.height()) {
            	
                minY = 0;
                
                maxY = Math.round(rect.height() - viewHeight);
                
            } else {
            	
                minY = maxY = startY;
                
            }

            mCurrentX = startX;
            
            mCurrentY = startY; 
 
            if (startX != maxX || startY != maxY) {
            	
                mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
                
            }
            
        }

        @Override
        public void run() {
        	
            if (mScroller.isFinished()) {
            	
                return;
                
            }

            ImageView imageView = getImageView();
            
            if (null != imageView && mScroller.computeScrollOffset()) {

                final int newX = mScroller.getCurrX();
                
                final int newY = mScroller.getCurrY(); 

                mSuppMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY);
                
                setImageViewMatrix(getDrawMatrix());

                mCurrentX = newX;
                
                mCurrentY = newY;
 
                Compat.postOnAnimation(imageView, this);
                
            }
            
        }
        
    }
    
}
